#!/usr/bin/python3
###############################################################################
'''滤波器电路参数求解的基础库'''
# Copyright (c) 2022 Xu Ruijun | 1687701765@qq.com
#
# This file is part of Electronic Analog Filter Design Tool(eAFDTool).
#
# eAFDTool is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or any later version.
#
# eAFDTool is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# eAFDTool. If not, see <https://www.gnu.org/licenses/>.
###############################################################################

__author__ = "Xu Ruijun"
__copyright__ = "Copyright (c) 2022 Xu Ruijun"
__license__ = "GPLv3 or later"


def parser_dict_kv1(d):
    assert len(kwargs) == 1
    k = list(kwargs)[0]
    v = kwargs[k]
    return k, v


class analog_filter:
    '''
    滤波器传递函数规范：
    一些滤波器传递函数比较混乱，很难直接看到一些信息，
    我提出一些规范，使得很容易看到增益值和角频率值

    基本要求：
    (1): 关于分子多项式：如果只有一项，则该项的系数为增益
    (2): 关于分母多项式：低通滤波器的常数项为1，高通滤波器的最高次项的系数为1
    (3): 分母多项式多于一项的带阻滤波器，分母多项式的最高次项的系数或常数项至少有一个为1,
         如果该滤波器有实际用途，有以下建议：
             低于阻带的信号为主时，使用(2)中低通滤波器的做法
             高于阻带的信号为主时，使用(2)中高通滤波器的做法
    其他要求：
    (4): 如果传递函数的系数是实际数值，分子多项式或分母多项式的有些系数的绝对值很大或很小时：
        如果常数项一般大小，除了常数项，系数的指数与s的指数为相反数
        如果最高次项的系数一般大小，除了最高次项，系数的指数与s的指数的和等于多项式次数
    (5): 如果传递函数的系数是电路标号RxLxCx，建议每种元件类型单独一行，虽然不符合一般数学表达式形式
    对于分子多项式多于一项的带通滤波器暂时不提出规范
    对于分子只有一项的低通或高通滤波器(1)与(2)应该不会产生矛盾，如有误请指出
    对于高通滤波器无法使用(4)

    一些例子：
    带通，符合(1)：
         ks
    ────────────
    as² + bs + c

    低通，违反(1),(2)：
      31.42
    ─────────
    s + 31.42

    带阻，符合(3)：
      s² + 1
    ──────────
    s² + s + 2

    低通，符合(1),(2),(4)：
         1                      1
    ────────── = ──────────────────────────────
    (100⁻¹+s)³   100⁻³s³ + 70.7⁻²s² + 50⁻¹s + 1

    高通，符合(1),(2),(4)：
        s³                s³
    ──────── = ─────────────────────────
    (s+100)³   s³ + 200s² + 141²s + 100³
    100rad/s即是上面两个滤波器角频率值

    低通，符合(1),(2),(5):
               1
    ─────────────────────────
    (C1 C2)  + (C2 + C2)  + 1
    (R1 R2)s²  (R1   R2)s

    以我的使用经验，scipy signal包生成的传递函数只有在角频率为1的时候才会遵守这些规范
    求解电路参数前，传递函数都要转化成这种规范的形式
    '''
    def __init__(self, B, A, ftype=None, omega=None, gain=None):
        assert 1 <= len(B) <= len(A)
        self.Nord = len(A) - 1
        if ftype is None:
            if len(B) == 1:                          # 是低通滤波器
                ftype = 'lowpass'
            elif len(B) == len(A) and not any(B[1:]):  # 是高通滤波器
                ftype = 'highpass'
            elif (len(B) - 1)*2 == self.Nord and not any(B[1:]):
                ftype = 'bandpass'
            else:
                raise TypeError('only support LPF, HPF, BPF')
        if omega is None and ftype in {'lowpass', 'highpass'}:
            omega = (A[-1]/A[0])**(1/self.Nord)
        if gain is None:
            if ftype == 'lowpass':
                gain = B[-1]/A[-1]
            elif ftype == 'highpass':
                gain = B[0]/A[0]
            elif ftype == 'bandpass':
                assert self.Nord == 2
                gain = B[1]/A[1]
            else:
                raise ValueError("can't solve gain")
        self.B = B
        self.A = A
        self.ftype = ftype
        self.omega = omega
        self.G = gain

    def format_gain(self):
        if self.ftype == 'lowpass':
            self.format_A1()
        elif self.ftype == 'highpass':
            self.format_A0()
        else:
            raise TypeError('only support LPF or HPF')

    def format_A1(self):
        self.B /= self.A[-1]
        self.A /= self.A[-1]

    def format_A0(self):
        self.B /= self.A[0]
        self.A /= self.A[0]

class RC_filter(analog_filter):
    '''
    这里提出一些变量使用规范：
    传递函数中的变量：
    分母多项式：a,b,c,d降幂排列
    增益：k

    时间常数：
    不能作为其他用途：x=R1C1, y=R2C2, z=R3C3
    其他时间常数：u,v,w
    '''
    # 请在子类中修改
    rcn = []
    t2rc = []
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.format_A1()
        self.t = None  # 时间常数
        self.rc = None  # RC数值

    def process_relat_vars(self, inputkw, retvars):
        if 'k_' in inputkw:
            raise ValueError("mean of keyword `k` is gain, can't use freq relat symbol `k_`")
        retd = inputkw.copy()
        for k in inputkw:
            if k[-1] == '_':
                retd[k[:-1]] = retd.pop(k) / self.omega
        rett = tuple(map(lambda vn:retd.pop(vn) if vn in retd else None, retvars))
        assert len(retd) == 0
        return rett

    def calc_rc(self, **kwargs):
        assert self.t.count(None) == 0
        rc = []
        for n in self.rcn:
            if n in kwargs:
                rc.append(kwargs[n])
            else:
                rc.append(None)
        assert rc.count(None) == len(rc) - 1  # 只有一个已知值
        for i in range(6):
            for ti, (ri, ci) in enumerate(self.t2rc):
                if rc[ri] is not None and rc[ci] is None:
                    rc[ci] = self.t[ti]/rc[ri]
                if rc[ci] is not None and rc[ri] is None:
                    rc[ri] = self.t[ti]/rc[ci]
            if hasattr(self, 'krat') and self.G is not None:
                ai, bi = self.krat  # rc[ai]/rc[bi] = self.G
                if rc[ai] is not None and rc[bi] is None:
                    rc[bi] = rc[ai]/self.G
                if rc[bi] is not None and rc[ai] is None:
                    rc[ai] = rc[bi]*self.G
            if rc.count(None) == 0:
                break
        else:
            raise RuntimeError('无法解RC值')
        # 检查结果
        for ti, (ri, ci) in enumerate(self.t2rc):
            assert abs(rc[ri]*rc[ci]/self.t[ti] - 1) <= 1e-8
        if hasattr(self, 'krat') and self.G is not None:
            ai, bi = self.krat
            assert abs(rc[ai]/rc[bi]/self.G - 1) <= 1e-8
        self.rc = rc
        return rc
